package com.whz.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.whz.gateway.common.convert.StringToJsonConverter;
import com.whz.gateway.common.web.GatewayResponseBody;
import com.whz.gateway.common.util.GatewayJsonUtils;
import com.whz.gateway.common.util.GreyUtils;
import com.whz.gateway.common.util.RegexUtils;
import com.whz.gateway.common.web.WebBodyUtils;
import com.whz.gateway.event.GatewayAlertEvent;
import com.whz.gateway.common.log.GatewayLog;
import com.whz.gateway.common.log.LogUtils;
import com.whz.generic.api.ApiResponseWrapper;
import com.whz.generic.api.ResponseOrigin;
import com.whz.generic.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 统计某个或者某种路由的处理时长
 *
 * @author whz
 */
@Slf4j
@Component
public class GlobalCostTimeMetricFilter extends AbstractGlobalFilter implements Ordered {
    public static final String COUNT_START_TIME = GlobalCostTimeMetricFilter.class.getName() + "_TIME";
    public static final String REQUEST_LOG_OBJECT = GlobalCostTimeMetricFilter.class.getName() + "_LOG";
    public static final String REQUEST_BODY_KEY = GlobalCostTimeMetricFilter.class.getName() + "_BODY";

    @Value("${gateway.alert.samples:80}")
    private int samples;

    @Override
    protected boolean shouldFilter(ServerWebExchange exchange) {
        return true;
    }

    @Override
    protected Mono<Void> apply(final ServerWebExchange origin, final GatewayFilterChain chain) {
        origin.getAttributes().put(COUNT_START_TIME, System.currentTimeMillis());
        // 全局灰度测试
        String greyFlag = GreyUtils.greyTestRibbon(origin);
        // 添加grey header
        ServerWebExchange exchange = GreyUtils.addGreyFlag(origin, greyFlag);

        // 添加灰度标签
        currentGatewayLog(exchange).setGreyFlag(greyFlag);

        // 重构response body
        ServerHttpResponse response = WebBodyUtils.exchangeResponseBody(exchange,
                                                                        this::whenError,
                                                                        new StringToJsonConverter(body -> {
                    String error = getError(exchange, body);

                    HttpStatus statusCode = getHttpStatus(exchange);

                    ApiResponseWrapper<GatewayResponseBody> tongyuResponse = GatewayResponseBody.builder()
                            .path(exchange.getRequest().getPath().toString())
                            .method(exchange.getRequest().getMethodValue())
                            .timestamp(System.currentTimeMillis())
                            .code(statusCode.value())
                            .message(statusCode.name())
                            .error(error)
                            .remoteIp(currentGatewayLog(exchange).getIp())
                            .build()
                            .getTongyuResponse(ResponseOrigin.service);

                    // 保存error达到日志
                    currentGatewayLog(exchange).setError(error);

                    // 5XX报警
                    if (requireAlert(statusCode)) {
                        SpringUtils.publishEvent(new GatewayAlertEvent(currentGatewayLog(exchange)));
                    }

                    return GatewayJsonUtils.toJson(tongyuResponse);
                }));

        // 修改response
        ServerWebExchange newExchange = exchange.mutate().response(response).build();

        final Mono<Void> result;
        if (WebBodyUtils.requestBodyJson(newExchange) && newExchange.getRequest().getHeaders().getContentLength() > 0) {
            result = WebBodyUtils.exchangeRequestBody(chain, newExchange, new StringToJsonConverter(b -> {
                final Object json = JSON.parse(b);
                // 保留request body
                if (json instanceof JSONObject) {
                    // V1 的接口是 json object
                    Map<String, Object> unmodifiableMap = Collections.unmodifiableMap((JSONObject) json);
                    newExchange.getAttributes().put(REQUEST_BODY_KEY, unmodifiableMap);
                }
                // 保存日志
                currentGatewayLog(newExchange).setRequestBody(json);
                return GatewayJsonUtils.toJson(json);
            }));
        } else {
            result = chain.filter(newExchange);
        }

        return result.doFinally(signalType -> {
            // 记录日志
            Long startTime = newExchange.getAttribute(COUNT_START_TIME);
            if (startTime != null) {
                long costTime = (System.currentTimeMillis() - startTime);
                GatewayLog curLog = currentGatewayLog(newExchange);
                curLog.setResponseTime(new Date());
                curLog.setExecuteTime(costTime);
                curLog.setResponseStatus(getHttpStatus(newExchange).value());
                log.info("[Global-Cost-Filter] Request-Cost: {} ms , gateway info \n {}", costTime, curLog);
            }
        });
    }

    private String getError(final ServerWebExchange exchange, final String body) {
        String error = "service inner error";
        if (WebBodyUtils.responseBodyText(exchange)) {
            // text/html
            error = RegexUtils.getBodyTextFromHtml(body);
        } else if (WebBodyUtils.responseBodyJson(exchange)) {
            // application/json
            JSONObject jsonObject = JSON.parseObject(body);
            if (jsonObject.containsKey("error_description")) {
                error = jsonObject.getString("error_description");
            } else if (jsonObject.containsKey("error")) {
                error = jsonObject.getString("error");
            } else {
                error = body;
            }
        }
        return error;
    }


    private HttpStatus getHttpStatus(final ServerWebExchange exchange) {
        HttpStatus statusCode = exchange.getResponse().getStatusCode();
        if (statusCode == null) {
            statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return statusCode;
    }

    private boolean whenError(final ServerWebExchange exchange) {
        return getHttpStatus(exchange).isError() && (WebBodyUtils.responseBodyText(exchange) || WebBodyUtils.responseBodyJson(exchange));
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    /**
     * 获取当前的gateway log 对象
     *
     * @param exchange ex
     * @return log 对象
     */
    public static GatewayLog currentGatewayLog(final ServerWebExchange exchange) {
        if (!exchange.getAttributes().containsKey(REQUEST_LOG_OBJECT)) {
            exchange.getAttributes().put(REQUEST_LOG_OBJECT, LogUtils.printLog(exchange));
        }
        return exchange.getRequiredAttribute(REQUEST_LOG_OBJECT);
    }

    /**
     * 保存的request body Map
     *
     * @param exchange exchange
     * @return request body Map
     */
    public static Map<String, Object> currentRequestBody(final ServerWebExchange exchange) {
        return exchange.getRequiredAttribute(REQUEST_BODY_KEY);
    }

    public boolean requireAlert(HttpStatus status) {
        if (status.is4xxClientError()) {
            return ThreadLocalRandom.current().nextInt(100) > samples;
        }
        return status.is5xxServerError();
    }
}